/*!
   \file

   \brief
      Manages assets, such as models, textures, and sounds.

   © 2011 Mark W. Gabby II

   This file is part of the Avion engine.

   The Avion engine is free software: you can redistribute it and/or modify it under the terms of
   the GNU General Public License as published by the Free Software Foundation, either version 3 of
   the License, or (at your option) any later version.

   The Avion engine is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
   without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
   the GNU General Public License for more details.

   You should have received a copy of the GNU General Public License along with the Avion Engine.
   If not, see <http://www.gnu.org/licenses/>.
*/
#include "Systems/Resource/Resource.hpp"

#include <sstream>

#include <gl/gl.h>
#include <gl/glu.h>
#include <gl/glext.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#include <fiff.hpp> // Flexible Interchange File Format library
#include "Avion FIFF IDs.hpp"

#include "Systems/Resource/AvionModel.hpp"
#include "Systems/Resource/AvionTexture.hpp"
#include "Systems/Platform/glCP.hpp"
#include "Systems/Platform/Platform.hpp"
#include "Systems/Graphics/Graphics.hpp"
#include "Systems/ErrorManager/ErrorManager.hpp"

#include "Util/ConstantsFile.hpp"
#include "Util/IOutils.hpp"
#include "Util/MemMappedFile.hpp"


Resource RESOURCES;

ModelData * Resource::GetModelData( const uint32_t Handle )
{
   return models[Handle];
}

Font * Resource::GetFont( const uint32_t Handle )
{
   return fonts[Handle];
}

void FiffErrorCallback( const char * const Message )
{
   Error( Message );
}
void FiffWarningCallback( const char * const Message )
{
   Warning( Message );
}

void Resource::LoadIndividualFiles()
{
   // Load the texture data.
   texturesPath = "Textures";
   CONSTANTS.Get( texturesPath, "TexturesFolder" );
   LoadIndividualTextures( texturesPath );

   // Now load the models.
   modelsPath = "Models";
   CONSTANTS.Get( modelsPath, "ModelsFolder" );
   LoadIndividualModels( modelsPath );

   // TODO: load the sounds.
   soundsPath = "Sounds";
   CONSTANTS.Get( soundsPath, "SoundsFolder" );

   // TODO: load the animations.
   animationsPath = "Animations";
   CONSTANTS.Get( animationsPath, "AnimationsFolder" );
}

void Resource::LoadAvnArchiveFiles()
{
   // Load the texture data.
   texturesArchivePath = "Textures.avnArchive";
   CONSTANTS.Get( texturesArchivePath, "TexturesArchiveFilePath" );
   LoadTexturesFromAvnArchive( texturesArchivePath );

   // Now load the models.
   modelsArchivePath = "Models.avnArchive";
   CONSTANTS.Get( modelsArchivePath, "ModelsArchiveFilePath" );
   LoadModelsFromAvnArchive( modelsArchivePath );

   soundsArchivePath = "Sounds.avnArchive";
   CONSTANTS.Get( soundsArchivePath, "SoundsArchiveFilePath" );

   animationsArchivePath = "Animations.avnArchive";
   CONSTANTS.Get( animationsArchivePath, "AnimationsArchiveFilePath" );
}

/*!
    \brief
    This function sets up the resource manager.

*/
bool Resource::Initialize()
{
   // Setup the FIFF library
   fiff::set_allocator( fiff::allocator_new );
   fiff::set_error_callback( FiffErrorCallback );
   fiff::set_warning_callback( FiffWarningCallback );

   LoadFonts();
   LoadIndividualFiles();
   LoadAvnArchiveFiles();

   return true;
}

/*!
    \brief
    This function loads the textures from a data file.

    \param TextureDataFilePath
    The path to the textures data file.
*/
bool Resource::LoadTexturesFromAvnArchive( const std::string & TextureDataFilePath )
{
   // Open the file for reading
   if ( fiff::file_begin( TextureDataFilePath.c_str(), fiff::kExisting ) == false )
   {
      // If we fail, return false
      return false;
   }

   // Get the first group
   fiff::read_group_begin();

   // If it's not a "Textures" or "Mixed" group, it's not a supported group to load from.
   const fiff::uChunkID64 TypeID = fiff::get_chunk_type_ID();
   if ( TypeID != AVION_ARCHIVE_BITMAPS_TYPE_ID &&
         TypeID != AVION_ARCHIVE_MIXED_TYPE_ID )
   {
      fiff::file_end();
      return false;
   }

   // For each texture...
   std::list<AvionTexture *> Textures;
   while ( fiff::read_object_begin( AVION_TEXTURE_MAP_TYPE_ID ) )
   {
      AvionTexture * CurTexture = new AvionTexture();
      CurTexture->LoadFromFIFFObject();
      Textures.push_back( CurTexture );

      fiff::read_object_end( AVION_TEXTURE_MAP_TYPE_ID );
   }

   // Create the texture object names for OpenGL.
   const uint32_t TextureCount = Textures.size();
   if ( TextureCount > 0 )
   {
      textureObjectNames = new uint32_t[TextureCount];
      glGenTextures( TextureCount, textureObjectNames );
      int TextureObjectNameIndex = 0;
      while ( Textures.empty() == false )
      {
         const AvionTexture * const CurTexture = Textures.back();
         Textures.pop_back();
         // Set the texture to this texture object
         BindTexture( *CurTexture, textureObjectNames[TextureObjectNameIndex] );

         // Now, assign the texture object index to the map for this texture's name.
         // If there's already a texture of this name... well... it's overwriting it.
         textureHandles[CurTexture->GetName()] = TextureObjectNameIndex;
         ++TextureObjectNameIndex;
         // Deallocate this texture - we don't need it anymore
         delete CurTexture;
      }
   }

   return true;
}

void Resource::BindTexture( const AvionTexture & Texture, const uint32_t TextureObjectName )
{
   // Bind the texture object to the following operations.
   glBindTexture( GL_TEXTURE_2D, TextureObjectName );
   uint32_t MipCount = Texture.GetMIPcount();

   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );

   float requestedAnisotropyLevel = GFX.GetAnisotropyLevel();
   float supportedAnisotropyLevel = CrossPlatformOpenGL::GetMaxAnisotropy();

   if ( supportedAnisotropyLevel )
   {
      // If we have enough supported levels, set to the requested level.
      if ( requestedAnisotropyLevel <= supportedAnisotropyLevel &&
            requestedAnisotropyLevel )
      {
         CrossPlatformOpenGL::SetAnisotropy( requestedAnisotropyLevel );
      }
      // Otherwise, set to the max level available.
      else
      {
         CrossPlatformOpenGL::SetAnisotropy( supportedAnisotropyLevel );
      }
   }

   // Go through all the mip map levels and load them for the current texture object.
   for ( uint32_t i = 0; i < MipCount; ++i )
   {
      CoordPair<uint32_t> dimensions = Texture.GetMIPdimensions( i );

      // Here's where the actual copying takes place.
      glTexImage2D( GL_TEXTURE_2D, i, GL_RGBA,
                    dimensions[kWidth], dimensions[kHeight],
                    0, GL_RGBA, GL_UNSIGNED_BYTE,
                    Texture.GetPixels( i ) );
   }
}

bool Resource::LoadIndividualTextures( const std::string & Folder )
{
   return false;
}

/*!
    \brief
    This function parses the Fonts List and loads each font in the list.

    \param FontsListFilePath
    Path and filename for the fonts list file.

    \return
    True on success, false on failure.
*/
bool Resource::LoadFonts()
{
   // Get the location of the fonts directory
   std::string FontsListFilePath = CONSTANTS.GetPath( "FontsFolder", NULL );

   // Attempt to load the FreeType library in order to load all the fonts.
   FT_Library FreeTypeLibrary;
   FT_Error LastErrorFT = FT_Init_FreeType( &FreeTypeLibrary );

   if ( LastErrorFT )
   {
      Error( "Failed to initialize freetype. Error code %i.", LastErrorFT );

      return false;
   }

   std::list<std::string> FontFilepaths = pf::GetDirectoryFilePaths( FontsListFilePath.c_str(), "avionFnt" );
   for ( std::list<std::string>::iterator it = FontFilepaths.begin(); it != FontFilepaths.end(); ++it )
   {
      Font * NewFont = new Font();

      std::string FontName;
      if ( NewFont->Load( FontName, *it, FreeTypeLibrary ) )
      {
         uint32_t CurFontCount = fonts.size();

         fontHandles[FontName] = CurFontCount;
         fonts.push_back( NewFont );
      }
      else
      {
         Warning( "Font %s failed to load.", it->c_str() );
      }
   }

   return true;
}

/*!
    \brief
    This function loads models from an .avnArchive file.

    \param ModelDataFilePath
    This is the path of the model .avnArchive file.
*/
bool Resource::LoadModelsFromAvnArchive( const std::string & ModelDataFilePath )
{
   // Open the file for reading
   if ( fiff::file_begin( ModelDataFilePath.c_str(), fiff::kExisting ) == false )
   {
      // If we fail, return false
      return false;
   }

   // Get the first group
   fiff::read_group_begin();

   // If it's not a "Models" or "Mixed" group, it's not a supported group to load from.
   const fiff::uChunkID64 TypeID = fiff::get_chunk_type_ID();
   if ( TypeID != AVION_ARCHIVE_MODELS_TYPE_ID &&
         TypeID != AVION_ARCHIVE_MIXED_TYPE_ID )
   {
      fiff::file_end();
      return false;
   }

   // For each object:
   int CurModelIndex = 0;
   while ( fiff::read_object_begin( AVION_MODEL_TYPE_ID ) )
   {
      AvionModel Model;
      Model.LoadFromFIFFObject();
      fiff::read_object_end( AVION_MODEL_TYPE_ID );

      // Load the model's data into OpenGL
      ModelData * Data = new ModelData;

      Data->bufferIndices = new uint32_t[Model.GetBufferCount()];
      Data->bufferCount = Model.GetBufferCount();
      const char * const ModelTextureName = Model.GetTextureMapName();
      Data->textureIndex = kNull32;
      if ( ModelTextureName[0] != '\0' )
      {
         Data->textureIndex = GetTextureObjectName( ModelTextureName );
      }
      const char * const ModelNormalMapName = Model.GetNormalMapName();
      if ( ModelNormalMapName[0] != '\0' )
      {
         Data->normalMapIndex = GetTextureObjectName( ModelNormalMapName );
      }
      Data->triangleCount = Model.GetTriangleCount();
      Data->boundingBox = Model.GetBoundingBox();

      // Generate buffers for the new Model.
      CrossPlatformOpenGL::glGenBuffers( Model.GetBufferCount(),
                                         Data->bufferIndices );

      // Load the vertices into a buffer.
      CrossPlatformOpenGL::glBindBuffer( GL_ARRAY_BUFFER, Data->bufferIndices[kVertices] );
      CrossPlatformOpenGL::glBufferData( GL_ARRAY_BUFFER, Model.GetVertexSizeBytes(),
                                         Model.GetVertexData(), GL_STATIC_DRAW );

      // Load the normals into a buffer.
      CrossPlatformOpenGL::glBindBuffer( GL_ARRAY_BUFFER, Data->bufferIndices[kNormals] );
      CrossPlatformOpenGL::glBufferData( GL_ARRAY_BUFFER, Model.GetNormalSizeBytes(),
                                         Model.GetNormalData(), GL_STATIC_DRAW );

      // Load the vertex colors into a buffer.
      CrossPlatformOpenGL::glBindBuffer( GL_ARRAY_BUFFER, Data->bufferIndices[kColors] );
      CrossPlatformOpenGL::glBufferData( GL_ARRAY_BUFFER, Model.GetVcolorSizeBytes(),
                                         Model.GetVcolorData(), GL_STATIC_DRAW );

//      // Load the specular colors into a buffer.
//      CrossPlatformOpenGL::glBindBuffer( GL_ARRAY_BUFFER, Data->bufferIndices[kSpecColors] );
//      CrossPlatformOpenGL::glBufferData( GL_ARRAY_BUFFER, Model.GetSpecColorSizeBytes(),
//                                         Model.GetSpecClrData(), GL_STATIC_DRAW );

      // Load the texture coordinates
      CrossPlatformOpenGL::glBindBuffer( GL_ARRAY_BUFFER, Data->bufferIndices[kTextureCoords] );
      CrossPlatformOpenGL::glBufferData( GL_ARRAY_BUFFER, Model.GetTextureCoordsSizeBytes(),
                                         Model.GetTextureCoords(), GL_STATIC_DRAW );

//      // Load the vertex specular coefficients
//      CrossPlatformOpenGL::glBindBuffer( GL_ARRAY_BUFFER, Data->bufferIndices[kCoefficients] );
//      CrossPlatformOpenGL::glBufferData( GL_ARRAY_BUFFER, Model.GetSpecPowerSizeBytes(),
//                                         Model.GetspecPowers(), GL_STATIC_DRAW );

      Data->specular = Model.GetSpecPowers()[0];

      Data->triangleIndices = new uint32_t[Model.GetTriangleCount() * 3];
      memcpy( Data->triangleIndices, Model.GetTriangleIndices(), Model.GetTriangleIndicesSizeBytes() );

      const std::string & Name = Model.GetName();

      modelHandles[Name] = CurModelIndex;
      models.push_back( Data );
      ++CurModelIndex;
   }

   fiff::read_group_end();
   fiff::file_end();

   return true;
}

bool Resource::LoadIndividualModels( const std::string & /*ModelsDirectory*/ )
{
   return true;
}

bool Resource::LoadModelsMemMapped( const std::string & /*ModelDataFilePath*/ )
{
   return true;
}

bool Resource::LoadAnimationsMemMapped( const std::string & /*AnimationDataFilePath*/ )
{

   return true;
}



bool Resource::LoadSoundsMemMapped( const std::string & /*SoundDataFilePath*/ )
{


   return true;
}

/*!
    \brief
    Returns an OpenGL texture object name. If the texture isn't loaded, it returns
    kNull32.

    \note
    The texture object name is NOT a string, it's a number that's used by OpenGL
    to uniquely identify a texture.
*/
uint32_t Resource::GetTextureObjectName( const char * const TextureName )
{
   std::map<std::string, uint32_t>::iterator handleIt = textureHandles.find( TextureName );
   if ( handleIt == textureHandles.end() )
   {
      if ( textureHandles.empty() )
      {
         Error( "Trying to get texture object name for texture %s when no textures have been "
                "loaded! Make sure your textures archive is valid.", TextureName );
      }
      else
      {
         Error( "No texture named %s found!", TextureName );
      }

      return kNull32;
   }

   uint32_t TexName = textureObjectNames[ handleIt->second ];

   if ( glIsTexture( TexName ) )
   {
      return TexName;
   }

   return kNull32;
}

/*!
    \brief
    Returns an index which can be used to index a model
    object. If the model isn't loaded, it returns
    kNull32.
*/
uint32_t Resource::GetModelHandle( const char * const ModelName )
{
   std::map<std::string, uint32_t>::iterator it = modelHandles.find( ModelName );
   if ( it == modelHandles.end() )
   {
      if ( modelHandles.empty() )
      {
         Error( "Trying to get handle to model %s when no models have been loaded! Make sure your "
                "models archive is valid.", ModelName );
      }
      else
      {
         Error( "No model named %s found!", ModelName );
      }
      return kNull32;
   }

   return it->second;
}
/*!
    \brief
    Returns an handle which can be used to get a font.
    If the font isn't loaded, it returns kNull32.
*/
uint32_t Resource::GetFontHandle( const char * const FontName )
{
   std::map<std::string, uint32_t>::iterator it = fontHandles.find( FontName );
   if ( it == fontHandles.end() )
   {
      if ( fontHandles.empty() )
      {
         Error( "Trying to get handle to font %s when no fonts have been loaded! Make sure your "
                "fonts folder is valid.", FontName );
      }
      else
      {
         Error( "No font named %s found!", FontName );
      }

      return kNull32;
   }

   return it->second;
}

void Resource::FreeAllTextures()
{
   if ( textureCount )
   {
      glDeleteTextures( textureCount, textureObjectNames );
      delete[] textureObjectNames;
      textureObjectNames = 0;
      textureCount = 0;
   }
}

void Resource::FreeAllSounds()
{

}

void Resource::FreeAllModels()
{
   uint32_t ModelCount = uint32_t( models.size() );

   for ( uint32_t i = 0; i < ModelCount; ++i )
   {
      if ( models[i]->triangleIndices )
      {
         delete[] models[i]->triangleIndices;
      }

      CrossPlatformOpenGL::glDeleteBuffers( models[i]->bufferCount,
                                            models[i]->bufferIndices );
   }

   models.clear();
}

Resource::Resource()
{
   textureCount = 0;
   textureObjectNames = 0;
}

Resource::~Resource()
{
   Close();
}

void Resource::Close()
{
   FreeAllTextures();
   FreeAllSounds();
   FreeAllModels();
}
